<?php
declare (strict_types=1);

namespace app\service;


use app\api\exception\ApiException;
use think\facade\Log;

class WxService
{
    protected $appId;           //微信支付申请对应的公众号的APPID
    protected $appKey;          //微信支付申请对应的公众号的APP Key
    protected $mchId;           //微信支付商户号

    protected $notifyUrl;       //异步回调地址

    protected $serialNo;        //证书编号
    protected $service_appId;
    protected $apiV3Key;
    protected $service_appSecret;
    protected $tokenUrl;
    protected $payUrl;
    protected $publicKeyContent;
    protected $publicKeyId;


    public function __construct()
    {
        $this->appId = env('PAY.APPID');
        $this->appKey = env('PAY.SECRET');;
        $this->notifyUrl = request()->domain() . '/api/notify';
        $this->apiV3Key = env('ISP.key');                     //APIv3密钥
        $this->service_appId = env('PAY.SERVICE_APPID');
        $this->service_appSecret = env('PAY.SERVICE_APPSECRET');
        $this->tokenUrl = env('PAY.TOKENURL');
        $this->payUrl = env('PAY.PAYURL');
        //$this->privateKey = file_get_contents($this->serialPath."apiclient_key.pem");
        $this->mchId = env('ISP.MCHID');
        $this->serialNo = env('ISP.SERIAL_NO');
        // 从文件读取公钥内容
        $this->publicKeyContent = file_get_contents("./cert/1721341154/pub_key.pem");
        $this->publicKeyId = env('ISP.PUBLIC_ID'); //公钥ID（从商户平台获取）

    }


    /**
     * 获取code
     * @return mixed
     */
    public function getCode()
    {
        //微信授权地址；
        if (empty(input('code'))) {
            //1.拼接auth 地址获取code码
            $urlObj['appid'] = $this->appId;
            $urlObj['redirect_uri'] = urlencode(request()->url(true));
            $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$urlObj['appid']}&redirect_uri={$urlObj['redirect_uri']}&response_type=code&scope=snsapi_base&state=&connect_redirect=1#wechat_redirect";
            Header("Location:$url");
            exit();
        } else {
            //2.获取user_id
            return input('code');
        }
    }


    /**
     * 认证获取用户信息,微信openid
     * @return mixed
     */
    public function getBuyerId($code)
    {
        $urlObj["appid"] = $this->appId;
        $urlObj["secret"] = $this->appKey;
        $urlObj["code"] = $code;
        $urlObj["grant_type"] = "authorization_code";
        $bizString = $this->ToUrlParams($urlObj);
        $res = self::request("https://api.weixin.qq.com/sns/oauth2/access_token?" . $bizString);
        //logs('获取的openid',$res);
        if (200 == $res['code'] && !empty($res['data'])) {
            //取出openid
            $data = json_decode($res['data'], true);
            if (!empty($data['openid'])) {
                return ['code' => 1, 'buyerId' => $data['openid']];
            }
            return ['code' => 0, 'msg' => '请求成功,但未获取到用户微信openid'];
        }
        return ['code' => 0, 'msg' => '请求失败,未获取到用户微信openid,httpCode:' . $res['code']];
    }


    /**
     * 拼接签名字符串
     * @param array $urlObj
     * @return 返回已经拼接好的字符串
     */
    private function ToUrlParams($urlObj)
    {
        $buff = "";
        foreach ($urlObj as $k => $v) {
            if ($k != "sign") $buff .= $k . "=" . $v . "&";
        }
        $buff = trim($buff, "&");
        return $buff;
    }


    /**-------------------------------------------------post,get请求-------------------------------------------------**/
    /**
     * post,get请求
     * @param string $url
     * @param string $method
     * @param array $options
     * @param array $data
     * @return bool|string
     */
    static function request($url = '', $method = 'GET', $options = array(), $data = '')
    {
        $curl = curl_init();
        //值请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $options);
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        if ($method == 'POST') {
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        $result['data'] = curl_exec($curl);
        $result['code'] = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);
        return $result;
    }


    /**-------------------------------------------------post,get请求-------------------------------------------------**/

    public function getToken()
    {
        $token = \think\facade\Cache::get($this->service_appId);
        if (!$token) {
            $ret = curl_http($this->tokenUrl . '?appId=' . $this->service_appId . '&appSecret=' . $this->service_appSecret);
            if (!$ret) {
                exit();
            }
            \think\facade\Cache::set($this->service_appId, $ret['result']['access_token'], $ret['result']['expire'] - 200);
            $token = $ret['result']['access_token'];
        }
        return $token;
    }



    /**---------------------------------------------------订单方法---------------------------------------------------**/
    /**
     * 二维码支付订单
     * @param $data
     * @return array
     */
    public function qrpayOrder($data)
    {
        $info = [
            'out_trade_no' => $data['order_no'],
            'sub_mchid' => $data['seller_id'],
            'total_fee' => intval(bcmul($data['price'], '100')),
            'notify_url' => $this->notifyUrl,
            'body' => $data['shop_name'],
            'sp_openid' => $data['buyerId'],
            'sp_name' => 'YXISP',
        ];
        $token = $this->getToken();
        $ret = curl_http($this->payUrl, true, json_encode($info), ['x-auth-data:' . $token]);
        if ($ret['code'] != 200) {
            throw new ApiException($ret['result']);
        }
        return $ret;
    }


    /*********************************微信支付平台证书验签已作废，改为公钥验签************************************************/
    /**
     * 验证微信支付回调签名
     */
    public function verifySign($headers, $body)
    {
        // 1. 获取HTTP头中的签名信息
        $signature = $headers['Wechatpay-Signature'] ?? '';
        $timestamp = $headers['Wechatpay-Timestamp'] ?? '';
        $nonce = $headers['Wechatpay-Nonce'] ?? '';
        $serialNo = $headers['Wechatpay-Serial'] ?? '';

        // 2. 检查公钥ID是否匹配
        if ($serialNo !== $this->publicKeyId) {
            throw new ApiException("公钥序列号不匹配: {$serialNo}");
        }

        // 3. 构造验签字符串
        $message = "{$timestamp}\n{$nonce}\n{$body}\n";
        // 4. 进行验签
        return $this->verifySignature($message, $signature);
    }


    /**
     * 使用公钥验证签名
     */
    private function verifySignature($message, $signature)
    {
        // 解码Base64格式的签名
        $decodedSignature = base64_decode($signature);
        $publicKeyResource = $this->publicKeyContent;

        // 使用公钥验签
        //这里只要 返回值是1即可，不要用openssl_error_string()来作为依据判断
        $result = openssl_verify(
            $message,
            $decodedSignature,
            $publicKeyResource,
            OPENSSL_ALGO_SHA256
        );
        if ($result === 1) {
            return true;
        } elseif ($result === 0) {
            throw new ApiException("回调数据被篡改！");
        } else {
            throw new ApiException("验签过程发生错误");
            //throw new ApiException("验签过程发生错误: " . openssl_error_string());
        }
    }


    /**
     * 处理回调的入口方法
     * @param $headers  微信回调时的header
     * @param array $body 微信回调时返回的数据
     * @return array
     */
    public function handleCallback($headers, $data)
    {
        try {
            if ($this->verifySign($headers, $data)) {
                // 验签成功，处理业务逻辑
                $res = json_decode($data, true);
                // 如果回调数据包含加密内容，进行解密
                if (isset($res['resource'])) {
                    $resource = $res['resource'];
                    $decryptedData = $this->decryptData(
                        $resource['ciphertext'],
                        $resource['associated_data'],
                        $resource['nonce']
                    );

                    // 将解密后的数据添加到结果中
                    $res['decrypted_data'] = json_decode($decryptedData, true);
                }
                return ['code' => 1, 'data' => $res];
            } else {
                // 验签失败
                return ['code' => 0, 'msg' => '签名验证失败'];
            }
        } catch (\Exception $e) {
            return ['code' => 0, 'msg' => $e->getMessage()];
        }
    }


    /**
     * 解密回调数据（如果需要）
     * 微信支付V3的回调数据中，敏感信息是加密的
     */
    public function decryptData($encryptedData, $associatedData, $nonce)
    {
        $ciphertext = base64_decode($encryptedData);
        if (strlen($this->apiV3Key) != 32) {
            throw new ApiException('无效的ApiV3Key，长度应为32字节');
        }
        // 提取认证标签(tag) - 最后16字节
        $tagLength = 16;
        if (strlen($ciphertext) <= $tagLength) {
            throw new ApiException('密文长度不足，无法提取认证标签');
        }

        $tag = substr($ciphertext, -$tagLength);
        $ciphertext = substr($ciphertext, 0, -$tagLength);

        // 使用AEAD_AES_256_GCM解密
        $decrypted = openssl_decrypt(
            $ciphertext,
            'aes-256-gcm',
            $this->apiV3Key,
            OPENSSL_RAW_DATA,
            $nonce,
            $tag,
            $associatedData
        );

        if ($decrypted === false) {
            throw new ApiException('解密失败: ' . openssl_error_string());
        }

        return $decrypted;
    }
}